package pcloud.httpclient.test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.lang.reflect.Field;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.nutz.json.Json;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.mapl.Mapl;

public class HttpClient
{
	private static final int Block_Size = 4096;
	private static final String Set_Cookie_Header = "Set-Cookie";
	private static final String Send_Cookie_Header = "Cookie";
	private static final String Session_Key = "JSESSIONID";
	private static final String BOUNDARY = "---------------------------41184676334";
	private HttpURLConnection conn = null;
	private String endPoint = null;
	private Map<String, String> cookies = null;

	public boolean login(String username, String password, String clientId)
	{
		if (username == null) {
			return false;
		}
		setPath("/users/login/" + username + "/" + clientId);
		StringBuffer sb = sendGetRequest();
		Map<String, String> map = (Map<String, String>) Json.fromJson(sb.toString());
		String resultUsername = map.get("user_name");
		if (resultUsername.equals(username)) {
			cookies = getCookies();
			return true;
		} else {
			return false;
		}
	}

	public StringBuffer sendRequest(String path)
	{
		return sendRequest(path, "GET", null);
	}

	public StringBuffer sendRequest(String path, String method, Object pojo)
	{
		return sendRequest(path, method, pojo, null);
	}

	public StringBuffer sendRequest(String path, String method, Object pojo, File file)
	{
		if (conn == null) {
			throw Lang.makeThrow("sent request failed! please setUrl first!");
		}

		setPath(path);
		// 对于POST方法, 设置对应的header信息
		String content = null;
		if (method.equals("POST") || method.equals("PUT")) {
			conn.setUseCaches(false);
			if (file == null) {
				conn.setRequestProperty("Content-Type", "application/json");
				content = Json.toJson(pojo);
			} else {
				conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
				conn.setRequestProperty("connection", "Keep-Alive");
				conn.setChunkedStreamingMode(Block_Size); // 告诉HttpUrlConnection,我们需要采用流方式上传数据，无需本地缓存数据。HttpUrlConnection默认是将所有数据读到本地缓存，然后再发送给服务器，这样上传大文件时就会导致内存溢出。
				content = buildMultiPartData(pojo);
			}
		}

		StringBuffer result = new StringBuffer();
		try {
			conn.setRequestMethod(method);
			conn.connect();

			// 对于POST方法, 写入输出流
			if (method.equals("POST") || method.equals("PUT")) {
				OutputStream os = conn.getOutputStream();
				BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os));
				writer.write(content);
				writer.flush();
				if (file != null) {
					InputStream fis = new FileInputStream(file); // 读取文件内容，发送数据，数据要一点点发送，不能一下全部读取发送，否则会内存溢出。
					int rn;
					byte[] buf = new byte[Block_Size];
					while ((rn = fis.read(buf, 0, Block_Size)) > 0) {
						os.write(buf, 0, rn);
					}

					byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
					os.write(end_data);
				}
				// os.flush();
				// os.close();

				writer.close();
			}

			// 取得输入流，并使用Reader读取
			InputStream inputStream = conn.getInputStream();

			//根据content-type判断如何处理输入流
			String contentType = conn.getHeaderField("Content-Type");
			if (contentType.startsWith("application/octet-stream")) {
				if (file == null) {
					throw Lang.makeThrow("No file identifier, can not handle response stream from url '%s'!", path);
				}

				result.append("Get file length : ");
				
				byte[] bytes = new byte[1024];
				long totalLen=0;
				int bufferLen;
				if (!file.exists()) {
					file.createNewFile();
				}
				OutputStream outputStream = new FileOutputStream(file);
				while ((bufferLen = inputStream.read(bytes)) > 0) {
					outputStream.write(bytes, 0, bufferLen);
					totalLen += bufferLen;
				}
				inputStream.close();
				outputStream.close();
				
				result.append(totalLen);
				
			} else {
				BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
				String line;
				while ((line = reader.readLine()) != null) {
					result.append(line);
				}
				reader.close();
			}
			// 断开连接
			conn.disconnect();

		} catch (FileNotFoundException e) {
			return new StringBuffer("HTTP ERROR: 404! File not found!");
		} catch (IOException e) {
			throw Lang.makeThrow("sent request failed by IOException!\r\n" + e.getLocalizedMessage());
		}

		if (result.indexOf("No user info") > -1) {
			throw Lang.makeThrow("Not login in! ");
		}
		return result;
	}

	private String buildMultiPartData(Object pojo)
	{
		StringBuffer sb = new StringBuffer();

		Mirror<? extends Object> mr = Mirror.me(pojo);
		Field[] fields = mr.getFields();
		for (Field field : fields) {
			Object value = mr.getValue(pojo, field);
			if (value == null) {
				continue;
			}
			sb.append("--").append(BOUNDARY).append("\r\n");
			sb.append("Content-Disposition: form-data; name=\"" + field.getName() + "\"\r\n\r\n");
			sb.append(value);
			sb.append("\r\n--").append(BOUNDARY).append("\r\n");
		}
		sb.append("--").append(BOUNDARY).append("\r\n");
		sb.append("Content-Disposition: form-data; name=\"fileContent\"; filename=\"identify.zip\"\r\n");
		sb.append("Content-Type: application/octet-stream\r\n\r\n");
		return sb.toString();
	}

	public void setEndPoint(String endPoint)
	{
		if (endPoint.startsWith("http://")) {
			this.endPoint = endPoint;
		} else {
			this.endPoint = "http://" + endPoint;
		}
	}

	public String getEndPoint()
	{
		return endPoint;
	}

	public void setPath(String path)
	{
		setUrl(endPoint + path);
	}

	public void setUrl(String url)
	{
		conn = getHttpConnection(url);
		conn.setDoOutput(true);
		conn.setDoInput(true);
		conn.setRequestProperty("Charsert", "UTF-8");
		conn.setRequestProperty("user-agent",
				"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"); // 可以不指定
		conn.setRequestProperty("Accept", "text/html;text/plain;application/json");
		conn.setAllowUserInteraction(false); // 无需用户交互，即弹出https等的对话框。
		if (cookies != null) {
			String sessionId = cookies.get(Session_Key);
			if (sessionId != null) {
				conn.setRequestProperty(Send_Cookie_Header, Session_Key + "=" + sessionId);
			}
		}
	}

	private StringBuffer sendGetRequest()
	{
		try {
			conn.setRequestMethod("GET");
			conn.connect();
			// 取得输入流，并使用Reader读取
			StringBuffer result = new StringBuffer();
			BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
			String line;
			while ((line = reader.readLine()) != null) {
				result.append(line);
			}
			reader.close();
			// 断开连接
			conn.disconnect();
			return result;
		} catch (FileNotFoundException e) {
			return new StringBuffer("HTTP ERROR: 404! File not found!");
		} catch (IOException e) {
			throw Lang.makeThrow("sent request failed by IOException!\r\n" + e.getLocalizedMessage());
		}
	}

	private HttpURLConnection getHttpConnection(String urlString)
	{
		try {
			URL url = new URL(urlString);
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();
			if ("https".equalsIgnoreCase(url.getProtocol())) {
				HttpsURLConnection httpsConn = (HttpsURLConnection) conn;
				TrustManager[] managers = { new MyX509TrustManager() };
				SSLContext sslContext = SSLContext.getInstance("TLS");
				sslContext.init(null, managers, new SecureRandom());
				SSLSocketFactory ssf = sslContext.getSocketFactory();
				httpsConn.setSSLSocketFactory(ssf);
				httpsConn.setHostnameVerifier(new MyHostnameVerifier());
				return httpsConn;
			} else {
				return conn;
			}
		} catch (IOException e) {
			throw Lang.makeThrow("Get http connection failed with IOException!");
		} catch (NoSuchAlgorithmException e) {
			throw Lang.makeThrow("Get http connection failed with NoSuchAlgorithmException!");
		} catch (KeyManagementException e) {
			throw Lang.makeThrow("Get http connection failed with KeyManagementException!");
		}
	}

	private Map<String, String> getCookies()
	{
		String cookieStr = conn.getHeaderField(Set_Cookie_Header);
		return parseCookie(cookieStr);
	}

	private Map<String, String> parseCookie(String str)
	{
		Map<String, String> map = new HashMap<String, String>();
		String[] items = str.split(";");
		for (String item : items) {
			String[] iPart = item.trim().split("=");
			map.put(iPart[0].trim(), iPart[1].trim());
		}
		return map;
	}

	public void upload(String urlString, String username, String password, File f)
	{

		String BOUNDARY = "---------------------------41184676334";
		byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();

		StringBuffer sb = new StringBuffer();

		sb.append("--").append(BOUNDARY).append("\r\n");
		sb.append("Content-Disposition: form-data; name=\"username\"\r\n\r\n");
		sb.append(username);
		sb.append("\r\n--").append(BOUNDARY).append("\r\n");

		sb.append("--").append(BOUNDARY).append("\r\n");
		sb.append("Content-Disposition: form-data; name=\"password\"\r\n\r\n");
		sb.append(password);
		sb.append("\r\n--").append(BOUNDARY).append("\r\n");

		sb.append("--").append(BOUNDARY).append("\r\n");
		sb.append("Content-Disposition: form-data; name=\"fileContent\"; filename=\"identify.zip\"\r\n");
		sb.append("Content-Type: application/octet-stream\r\n\r\n");

		byte[] data = sb.toString().getBytes();
		long fLen = f.length();
		long contentLen = data.length + fLen + end_data.length;

		HttpURLConnection conn;
		try {
			conn = getHttpConnection(urlString);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setRequestMethod("POST");
			conn.setRequestProperty("connection", "Keep-Alive");
			conn.setRequestProperty("Charsert", "UTF-8");
			conn.setRequestProperty("user-agent",
					"Mozilla/5.0 (Windows NT 6.1; WOW64; rv:12.0) Gecko/20100101 Firefox/12.0"); // 可以不指定
			conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
			conn.setRequestProperty("Content-Length", String.valueOf(contentLen)); // 不清楚长度，可以不写

			conn.setAllowUserInteraction(false); // 无需用户交互，即弹出https等的对话框。
			conn.setChunkedStreamingMode(Block_Size); // 告诉HttpUrlConnection,我们需要采用流方式上传数据，无需本地缓存数据。HttpUrlConnection默认是将所有数据读到本地缓存，然后再发送给服务器，这样上传大文件时就会导致内存溢出。
			OutputStream os = conn.getOutputStream();
			os.write(data); // 发送非文件数据

			InputStream fis = new FileInputStream(f); // 读取文件内容，发送数据，数据要一点点发送，不能一下全部读取发送，否则会内存溢出。
			int rn;
			byte[] buf = new byte[Block_Size];
			while ((rn = fis.read(buf, 0, Block_Size)) > 0) {
				os.write(buf, 0, rn);
			}
			os.write(end_data);
			os.flush();
			os.close();
			fis.close();

		} catch (ProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	public void close()
	{
		conn.disconnect();
	}

}
